/*
 * 本程序存储在软盘0的第1-5个扇区，共占用4个扇区的位置大小
 * 由 boot.s 加载到 0x90200 的位置，并跳转到此位置执行
 * 本程序所做的工作内容如下：
 *      1. 使用BIOS中断获取系统显示/内存/硬盘等相关参数信息，存放在 0x90000 开始的地址
 *      2. 将 system 从 0x10000 处搬移到 0x00000 处
 *      3. 开启 A20 地址线，以便能访问到 1M 以上的内存
 *      4. 临时重新设置 idt 和 gdt，进入保护模式需要使用
 *      5. 重新设置 8259A 的中断向量号为 0x20-0x2f
 *      6. 设置cr0的PE位，使用长跳转进入保护模式的代码段，地址0x0000处开始执行



    as --gstabs --32 -o setup.o setup.s
    ld -m elf_i386 -Ttext 0x0000 setup.o -g -o setup.elf
    objcopy -O binary setup.elf setup.bin

    dd if=setup.bin of=a.img bs=512 seek=1 conv=notrunc

 */

.equ INITSEG, 0x9000
.equ SETUPSEG, 0x9020
.equ SYSSEG, 0x1000

.globl begtext, begdata, begbss, endtext, enddata, endbss, _start
.text
begtext:
.data
begdata:
.bss
begbss:
.text


.code16
_start:
    /* %ds 本身实际在 boot.s 跳转过来的时候已经指向了 0x9000 了。此处还是设置了一下 */
    movw $INITSEG, %ax
    movw %ax, %ds

    /*
        获取内存大小（扩展内存，单位 KB）
	    功能：检测从 1MB（0x100000）开始的扩展内存大小。
	    返回值：ax 寄存器返回扩展内存的大小，单位为 KB
	    限制：最多检测到 64MB 的内存，如果实际内存超过 64MB，也会返回 64MB
	    如果调用成功，CF（进位标志）会被清零；如果调用失败，CF 会被置位，ax 中会返回错误码
     */
    movb $0x88, %ah
    int $0x15
    movw %ax, (2)

    /* 检查 EGA/VGA 和相关参数 */
    /*
        BIOS 中断 int 0x10，ah=0x12 的功能是用于检测显示卡类型（EGA/VGA）并获取一些配置参数
	    返回值：
	        bh：显示状态。0x00 表示彩色模式，0x01 表示单色模式。
	        bl：安装的显示内存大小。0x00 表示 64KB，0x01 表示 128KB，0x02 表示 192KB，0x03 表示 256KB
	            依此类推，公式为 show_mem_size = (bl + 1) * 64KB。
	        cx：显示卡特性参数
     */
    movb $0x12, %ah
    movb $0x10, %bl
    int $0x10

    movw %ax, (8)               # [0x90000 + 8] = %ax = 返回值
    movw %bx, (10)              # [0x90000 + 10] = %bx = 显示状态:安装的显存大小
    movw %cx, (12)              # [0x90000 + 12] = %cx = 显示卡特性参数

    movw $0x5019, %ax
    cmpb $0x10, %bl             # 判断显存是否是 1M 大小
    je novga
    call chsvga
novga:
    movw %ax, (14)              # [0x90000 + 14] = 0x5019
    /*
        读取光标位置：
        光标位置信息存储在 dh（行号）和 dl（列号）中
     */
    movb $0x03, %ah
    xor %bh, %bh
    int $0x10
    movw %dx, (0)               # [0x90000] = %dx,[0x90000] = 光标列号和行号

    /* 获取显卡参数 */
    movb $0x0f, %ah
    int $0x10
    movw %bx, (4)               # [0x90000 + 4] = 显示页
    movw %ax, (6)               # [0x90000 + 6] = 窗口宽度:显示模式

    movw $0x0000, %ax
    movw %ax, %ds               # %ds = 0x0000
    /*
        获取hd0硬盘参数
            [4*0x41] 指向的是 0x0000:[4*0x41] 内存位置，也就是 BIOS 中断 0x41 中断向量位置
	        此处存放的并不是中断向量的地址，而是第一个硬盘的基本参数表的段地址和偏移
	        因此下面语句的作用就是把该内存位置处的段:偏移加载到%ds:%si中，作为字节拷贝的源位置
	        硬盘基本参数表存放如下信息，总共16个字节：
                硬盘的柱面数
                硬盘的磁头数
                硬盘的扇区数
                硬盘的起始扇区号
                硬盘的分区表信息
     */
    lds (4*0x41), %si               # %ds = [4*0x41], %si = [4*0x41 + 2]
    movw $INITSEG, %ax
    movw %ax, %es
    movw $0x0080, %di               # %es:%di = 0x9000:0x0080 第一个硬盘参数信息拷贝到此处
    movw $0x10, %cx                 # 复制16个字节
    rep movsb

    /* 获取硬盘1参数 */
    movw $0x0000, %ax
    movw %ax, %ds
    lds (4*0x46), %si
    movw $INITSEG, %ax
    movw %ax, %es
    movw $0x0090, %di               # %es:%di = 0x9000:0x0090 第二个硬盘参数信息拷贝到此处
    movw $0x10, %cx
    rep movsb

    /*
        检查是否存在硬盘hd1
        功能号：ah = 0x15 的 int 0x13 中断，表示获取驱动器类型
        输入参数：
	        dl：驱动器号（0x80 表示第一个硬盘，0x81 表示第二个硬盘等）
	    返回值：
            ah：类型码
                0x00 - 没有这个盘 CF置位
                0x01 - 软盘（没有change_line支持）
                0x02 - 软盘（有change_line支持）
                0x03 - 硬盘
     */
    movw $0x1500, %ax
    movb $0x81, %dl
    int $0x13
    jc no_disk1             # CF=1，则跳转到 no_disk1
    cmpb $3, %ah            # 判断是否是硬盘
    je is_disk1
no_disk1:
    movw $INITSEG, %ax
    movw %ax, %es
    movw $0x0090, %di
    movw $0x10, %cx
    movw $0x00, %ax
    /*
        将 %es:%di 赋值为 %al，也就是把 0x9000:0x0090 起始的 16 个字节全部赋值为0
	    因为这里判定是没有 disk1 存在，因此前面从 BIOS 赋值的硬盘2参数是无法使用的，需要把这段内存赋值为0
     */
    rep stosb
is_disk1:

    /* 
        现在，终于要进入保护模式了 
        在进入保护模式之前，要先关中断，因为会对 idt 和 gdt 表做临时重新设置
     */
    cli

    /* 先把 system 移动到 0x0000 开始的位置 */
    movw $0x0000, %ax
    cld                         # 复制的方向是递增
do_move:
    movw %ax, %es               # 复制的目的位置
    addw $0x1000, %ax
    cmpw $0x9000, %ax           # 这里实际是在校验，拷贝结束的段地址不能超过 0x9000, 也就是最大 0x8000，共 0x8000 * 0x10 = 512 KB大小
    jz end_move                 # 已经拷贝足够的字节，则结束
    movw %ax, %ds               # 从 0x1000 段开始拷贝，注意，之前 system 是被 boot 加载到 0x1000 开始的段 
    sub %di, %di
    sub %si, %si
    movw $0x8000, %cx           # # 每次拷贝 0x8000 = 32K个字，也就是 64KB 数据（刚好是一个段）
    rep movsw
    jmp do_move

end_move:
    /*
        已经拷贝完成，接下来就是为进入保护模式做准备
        主要动作：
        准备 idt 和 gdt 并加载到对应的寄存器
        准备8259A 中断
     */
    movw $SETUPSEG, %ax
    movw %ax, %ds

    /*
        接下来就是加载 idt 表和 gdt 表；都是当前为了进入32位保护模式而临时设置的
	    等代码运行正式进入内核的 head.s ,还会重新做一次设置
	    这两个指令分别将 表长度和表基地址加载到 对应的寄存器；其中表长度是低2字节，表基地址是高4字节，因此
	        idt_48 和 gdt_48 都是6个字节，从命令 48 bit 也可以看出来
     */
    lidt idt_48
    lgdt gdt_48

    /* 开启 A20 地址线 */
    call empty_8042                 # 测试 8042 状态寄存器，等待其缓存区为空，才能做写入操作
    movb $0xd1, %al
    outb %al, $0x64                 # 先写入命令字
    call empty_8042
    movb $0xdf, %al
    outb %al, $0x60                 # A20开启
    call empty_8042


    /*
        重新设置 8259A 外设硬件中断向量号
        重设8259A中断向量号的原因是因为
        默认 BIOS 设置的 8259A 中断向量号 与处理器异常中断向量号有冲突
     */
    movb $0x22, %al                     # 初始化序列
    outb %al, $0x20                     # 发送到 8259A-1（主片8259A）
    .word 0x00eb, 0x00eb                # jmp $+2, jmp $+2，直接把命令编码了
    outb %al, $0xa0                     # 发送到 8259A-2（从片8259A）
    .word 0x00eb, 0x00eb
    movb $0x20, %al
    outb %al, $0x21                     # 主片的中断起始号（0x20）
    .word 0x00eb, 0x00eb
    movb $0x28, %al
    outb %al, $0xa1                     # 从片的中断起始号（0x28）
    .word 0x00eb, 0x00eb
    movb $0x04, %al
    outb %al, $0x21                     # 8259A-1 是主片
    .word 0x00eb, 0x00eb
    movb $0x02, %al
    outb %al, $0xa1                     # 8259A-1 是从片
    .word 0x00eb, 0x00eb
    movb $0x01, %al
    outb %al, $0x21                     # 设置 8086 模式
    .word 0x00eb, 0x00eb
    outb %al, $0xa1
    .word 0x00eb, 0x00eb
    movb $0xff, %al
    outb %al, $0x21
    .word 0x00eb, 0x00eb
    outb %al, $0xa1

/* ==============================  进入保护模式  =================================================*/
    movl $0x0001, %eax
    movl %eax, %cr0
    ljmp $8, $0

/*================================================================================================*/

empty_8042:
    .word 0x00eb, 0x00eb
    inb $0x64, %al              # 读取 8042 的状态端口
    testb $2, %al               # 输入缓存队列是否满
    jnz empty_8042
    ret

/* 以下是对显卡的相关操作，不关注了 */
chsvga:
    cld
    push %ds
    push %cs
    pop %ds
    movw $0xc000, %ax
    movw %ax, %es
    leaw msg1, %si
    call prtstr
nokey:
    inb $0x60, %al
    cmpb $0x82, %al
    jb nokey
    cmpb $0xe0, %al
    ja nokey
    cmpb $0x9c, %al
    je svga
    movw $0x5019, %ax
    pop %ds
    ret

svga:
    leaw idati, %si        # Check ATI 'clues'
    movw $0x31, %di
    movw $0x09, %cx
    repe
    cmpsb
    jne noati
    leaw dscati, %si
    leaw moati, %di
    leaw selmod, %cx
    jmp *%cx
noati:
    movw $0x200f, %ax      # Check Ahead 'clues'
    movw $0x3ce, %dx
    outw %ax, %dx
    incw %dx
    inb %dx, %al
    cmpb $0x20, %al
    je isahed
    cmpb $0x21, %al
    jne noahed
isahed:
    leaw dscahead, %si
    leaw moahead, %di
    leaw selmod, %cx
    jmp *%cx
noahed:
    movw $0x3c3, %dx       # Check Chips & Tech. 'clues'
    inb %dx, %al
    orb $0x10, %al
    outb %al, %dx
    movw $0x104, %dx
    inb %dx, %al
    movb %al, %bl
    movw $0x3c3, %dx
    inb %dx, %al
    andb $0xef, %al
    outb %al, %dx
    cmpb %bl, idcandt
    jne nocant
    leaw dsccandt, %si
    leaw mocandt, %di
    leaw selmod, %cx
    jmp *%cx
nocant:
    movw $0x3d4, %dx       # Check Cirrus 'clues'
    movb $0x0c, %al
    outb %al, %dx
    incw %dx
    inb %dx, %al
    movb %al, %bl
    xorw %ax, %ax
    outb %al, %dx
    decw %dx
    movb $0x1f, %al
    outb %al, %dx
    incw %dx
    inb %dx, %al
    movb %al, %bh
    xorb %ah, %ah
    sal $4, %al
    movw %ax, %cx
    movb %bh, %al
    shrb $4, %al
    addw %ax, %cx
    sal $8, %cx
    addw $6, %cx
    movw %cx, %ax
    movw $0x3c4, %dx
    outw %ax, %dx
    incw %dx
    inb %dx, %al
    testb %al, %al
    jnz nocirr
    movb %bh, %al
    outb %al, %dx
    inb %dx, %al
    cmpb $0x01, %al
    jne nocirr
    call rst3d4
    leaw dsccirrus, %si
    leaw mocirrus, %di
    leaw selmod, %cx
    jmp *%cx
rst3d4:
    movw $0x3d4, %dx
    movb %bl, %al
    xorb %ah, %ah
    sal $8, %ax
    addw $0x0c, %ax
    outw %ax, %dx
    ret
nocirr:
    call rst3d4            # Check Everex 'clues'
    movw $0x7000, %ax
    xorw %bx, %bx
    int $0x10
    cmpb $0x70, %al
    jne noevrx
    shr $4, %dx
    cmpw $0x678, %dx
    je istrid
    cmpw $0x236, %dx
    je istrid
    leaw dsceverex, %si
    leaw moeverex, %di
    leaw selmod, %cx
    jmp *%cx
istrid:
    leaw ev2tri, %cx
    jmp *%cx
noevrx:
    leaw idgenoa, %si      # Check Genoa 'clues'
    xorw %ax, %ax
    movb %es:0x37(%si), %al
    movw %ax, %di
    movw $0x04, %cx
    decw %si
    decw %di
l1:
    incw %si
    incw %di
    movb (%si), %al
    andb %es:(%di), %al
    cmpb (%si), %al
    loopne l1
    cmpw $0x00, %cx
    jne nogen
    leaw dscgenoa, %si
    leaw mogenoa, %di
    leaw selmod, %cx
    jmp *%cx
nogen:
    leaw idparadise, %si   # Check Paradise 'clues'
    movw $0x7d, %di
    movw $0x04, %cx
    repe
    cmpsb
    jne nopara
    leaw dscparadise, %si
    leaw moparadise, %di
    leaw selmod, %cx
    jmp *%cx
nopara:
    movw $0x3c4, %dx       # Check Trident 'clues'
    movb $0x0e, %al
    outb %al, %dx
    incw %dx
    inb %dx, %al
    xchgb %ah, %al
    movb $0x00, %al
    outb %al, %dx
    inb %dx, %al
    xchgb %ah, %al
    movb %al, %bl
    andb $0x02, %bl
    jz setb2
    andb $0xfd, %al
    jmp clrb2
setb2:
    orb $0x02, %al
clrb2:
    outb %al, %dx
    andb $0x0f, %ah
    cmpb $0x02, %ah
    jne notrid
ev2tri:
    leaw dsctrident, %si
    leaw motrident, %di
    leaw selmod, %cx
    jmp *%cx
notrid:
    movw $0x3cd, %dx       # Check Tseng 'clues'
    inb %dx, %al
    movb %al, %bl
    movb $0x55, %al
    outb %al, %dx
    inb %dx, %al
    movb %bl, %al
    outb %al, %dx
    cmpb $0x55, %ah
    jne notsen
    leaw dsctseng, %si
    leaw motseng, %di
    leaw selmod, %cx
    jmp *%cx
notsen:
    movw $0x3cc, %dx       # Check Video7 'clues'
    inb %dx, %al
    movw $0x3b4, %dx
    andb $0x01, %al
    jz even7
    movw $0x3d4, %dx
even7:
    movb $0x0c, %al
    outb %al, %dx
    incw %dx
    inb %dx, %al
    movb %al, %bl
    movb $0x55, %al
    outb %al, %dx
    inb %dx, %al
    decw %dx
    movb $0x1f, %al
    outb %al, %dx
    incw %dx
    inb %dx, %al
    movb %al, %bh
    decw %dx
    movb $0x0c, %al
    outb %al, %dx
    incw %dx
    movb %bl, %al
    outb %al, %dx
    movb $0x55, %al
    xorl %eax, %eax
    movb $0xea, %ah
    xorb %ah, %al
    cmpb %bh, %al
    jne novid7
    leaw dscvideo7, %si
    leaw movideo7, %di
selmod:
    pushw %si
    leaw msg2, %si
    call prtstr
    xorw %cx, %cx
    movb (%di), %cl
    popw %si
    pushw %si
    pushw %cx
tbl:
    popw %bx
    pushw %bx
    movb %bl, %al
    subb %cl, %al
    call dprnt
    call spcing
    lodsw
    xchgb %ah, %al
    call dprnt
    xchgb %ah, %al
    pushw %ax
    movb $0x78, %al
    call prnt1
    popw %ax
    call dprnt
    call docr
    loop tbl
    popw %cx
    call docr
    leaw msg3, %si
    call prtstr
    popw %si
    addb $0x80, %cl
nonum:
    inb $0x60, %al          # Quick and dirty...
    cmpb $0x82, %al
    jb nonum
    cmpb $0x8b, %al
    je zero
    cmpb %cl, %al
    ja nonum
    jmp nozero
zero:
    subb $0x0a, %al
nozero:
    subb $0x80, %al
    decb %al
    xorb %ah, %ah
    addw %ax, %di
    incw %di
    pushw %ax
    movb (%di), %al
    int $0x10
    popw %ax
    sal $1, %ax
    addw %ax, %si
    lodsw
    popw %ds
    ret
novid7:
    popw %ds               # Here could be code to support standard 80x50,80x30
    movw $0x5019, %ax
    ret

# Routine that 'tabs' to next col.
spcing:
    movb $0x2e, %al
    call prnt1
    movb $0x20, %al
    call prnt1
    movb $0x20, %al
    call prnt1
    movb $0x20, %al
    call prnt1
    movb $0x20, %al
    call prnt1
    ret

# Routine to print asciiz-string at DS:SI
prtstr:
    lodsb
    testb %al, %al
    jz fin
    call prnt1
    jmp prtstr
fin:
    ret

# Routine to print a decimal value on screen, the value to be printed is put in al (i.e 0-255)
dprnt:
    pushw %ax
    pushw %cx
    xorb %ah, %ah
    movb $0x0a, %cl
    idivb %cl
    cmpb $0x09, %al
    jbe lt100
    call dprnt
    jmp skip10
lt100:
    addb $0x30, %al
    call prnt1
skip10:
    movb %ah, %al
    addb $0x30, %al
    call prnt1
    popw %cx
    popw %ax
    ret

# Part of above routine, this one just prints ascii al
prnt1:
    pushw %ax
    pushw %cx
    xorb %bh, %bh
    movw $0x01, %cx
    movb $0x0e, %ah
    int $0x10
    popw %cx
    popw %ax
    ret

# Prints <CR> + <LF>
docr:
    pushw %ax
    pushw %cx
    xorb %bh, %bh
    movb $0x0e, %ah
    movb $0x0a, %al
    movw $0x01, %cx
    int $0x10
    movb $0x0d, %al
    int $0x10
    popw %cx
    popw %ax
    ret

/*================ 下面是部分数据的定义 ========================*/
gdt:
    .quad 0x0000000000000000                # GDT 表第一项不用
    .quad 0x00c09a00000007ff                # 内核代码段，选择符为 0x08；段基址 0x0000,段限长 8M，可读可执行
    .quad 0x00c09200000007ff                # 内核数据段，选择符为 0x10；段基址 0x0000,段限长 8M，可读可写

idt_48:
    .word 0                                 # 段限长 = 0
    .word 0, 0                              # 段基址 0

/*
    段限长2K，能容纳 256 个段描述符
    gdt 表的基地址是 0x9XXXX，XXXX就是 gdt 偏移 + 512 字节
    对于此时，因为 cs ds 都是 0x9020，因此基地址要加上这个段偏移
    也就是最终的地址是 0x90200 + gdt，拆分成高低字节就是 0x200 + gdt, 0x9
 */
gdt_48:
    .word 0x800
    .word 512+gdt,0x9

msg1:	.asciz	"Press <RETURN> to see SVGA-modes available or any other key to continue."
		.byte	0x0d, 0x0a, 0x0a, 0x00
msg2:	.asciz	"Mode:  COLSxROWS:"
		.byte	0x0d, 0x0a, 0x0a, 0x00
msg3:	.asciz	"Choose mode by pressing the corresponding number."
		.byte	0x0d, 0x0a, 0x00

idati:		.asciz	"761295520"
idcandt:	.byte	0xa5
idgenoa:	.byte	0x77, 0x00, 0x66, 0x99
idparadise:	.asciz	"VGA="

#           Manufacturer:	  Numofmodes:	Mode:

moati:		.byte	0x02,	0x23, 0x33 
moahead:	.byte	0x05,	0x22, 0x23, 0x24, 0x2f, 0x34
mocandt:	.byte	0x02,	0x60, 0x61
mocirrus:	.byte	0x04,	0x1f, 0x20, 0x22, 0x31
moeverex:	.byte	0x0a,	0x03, 0x04, 0x07, 0x08, 0x0a, 0x0b, 0x16, 0x18, 0x21, 0x40
mogenoa:	.byte	0x0a,	0x58, 0x5a, 0x60, 0x61, 0x62, 0x63, 0x64, 0x72, 0x74, 0x78
moparadise:	.byte	0x02,	0x55, 0x54
motrident:	.byte	0x07,	0x50, 0x51, 0x52, 0x57, 0x58, 0x59, 0x5a
motseng:	.byte	0x05,	0x26, 0x2a, 0x23, 0x24, 0x22
movideo7:	.byte	0x06,	0x40, 0x43, 0x44, 0x41, 0x42, 0x45

#           msb = Cols lsb = Rows:
dscati:		.word	0x8419, 0x842c
dscahead:	.word	0x842c, 0x8419, 0x841c, 0xa032, 0x5042
dsccandt:	.word	0x8419, 0x8432
dsccirrus:	.word	0x8419, 0x842c, 0x841e, 0x6425
dsceverex:	.word	0x5022, 0x503c, 0x642b, 0x644b, 0x8419, 0x842c, 0x501e, 0x641b, 0xa040, 0x841e
dscgenoa:	.word	0x5020, 0x642a, 0x8419, 0x841d, 0x8420, 0x842c, 0x843c, 0x503c, 0x5042, 0x644b
dscparadise:.word	0x8419, 0x842b
dsctrident:	.word 	0x501e, 0x502b, 0x503c, 0x8419, 0x841e, 0x842b, 0x843c
dsctseng:	.word	0x503c, 0x6428, 0x8419, 0x841c, 0x842c
dscvideo7:	.word	0x502b, 0x503c, 0x643c, 0x8419, 0x842c, 0x841c
	
.text
endtext:
.data
enddata:
.bss
endbss:
